package com.bit.cfdi.web.vm.perfil;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.zkoss.bind.BindContext;
import org.zkoss.bind.BindUtils;
import org.zkoss.bind.annotation.BindingParam;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.ContextParam;
import org.zkoss.bind.annotation.ContextType;
import org.zkoss.bind.annotation.Init;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.image.AImage;
import org.zkoss.image.Image;
import org.zkoss.util.media.Media;
import org.zkoss.zk.ui.event.UploadEvent;
import org.zkoss.zk.ui.select.annotation.VariableResolver;
import org.zkoss.zk.ui.select.annotation.WireVariable;
import org.zkoss.zul.Messagebox;

import com.bit.cfdi.web.constants.CFDIConstants;
import com.bit.cfdi.web.domain.Contacto;
import com.bit.cfdi.web.domain.DBConstants;
import com.bit.cfdi.web.domain.Email;
import com.bit.cfdi.web.domain.Imagen;
import com.bit.cfdi.web.domain.Persona;
import com.bit.cfdi.web.domain.Telefono;
import com.bit.cfdi.web.domain.Usuario;
import com.bit.cfdi.web.services.ContactoService;
import com.bit.cfdi.web.services.EmailService;
import com.bit.cfdi.web.services.ImagenService;
import com.bit.cfdi.web.services.PersonaService;
import com.bit.cfdi.web.services.TelefonoService;
import com.bit.cfdi.web.services.UsuarioService;
import com.bit.cfdi.web.session.SessionUtils;
import com.bit.cfdi.web.utils.ByteUtils;
import com.bit.cfdi.web.utils.EncryptionUtils;
import com.bit.cfdi.web.utils.ImageUtils;

@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
public class PerfilViewModel {
	
	private Logger log = LogManager.getLogger(this.getClass().getName());

	//Spring variables
	@WireVariable private SessionUtils sessionUtils;
	@WireVariable private ImagenService imagenService;
	@WireVariable private EncryptionUtils encryptionUtils;
	@WireVariable private UsuarioService usuarioService;
	@WireVariable private PersonaService personaService;
	@WireVariable private ContactoService contactoService;
	@WireVariable private TelefonoService telefonoService;
	@WireVariable private EmailService emailService;
	
	//Variables
	private String oldPassword;
	private String oldPasswordError;
	private String newPassword;
	private String newPasswordError;
	private String repeatNewPassword;
	private String repeatNewPasswordError;
	private Usuario usuario;
	private Persona persona;
	private String nombre;
	private String apellidoPaterno;
	private String apellidoMaterno;
	private List<Telefono> telefonos;
	private List<Email> emails;
	private Image image;
	private Imagen imagenUsuario;
	
	@Init
	public void init() {
		usuario = (Usuario) sessionUtils.getFromSession(SessionUtils.USUARIO);
		persona = usuario.getPersona();
		retrieveInitialUserInfo();
		retrieveInitialProfileInfo();
	}
	
	//Getters - Setters
	public String getOldPassword() {
		return oldPassword;
	}
	
	@NotifyChange({"oldPassword","oldPasswordError"})
	public void setOldPassword(final String oldPassword) {
		oldPasswordError = null;
		this.oldPassword = oldPassword;
	}
	
	public String getOldPasswordError() {
		return oldPasswordError;
	}
	
	public String getNewPassword() {
		return newPassword;
	}
	
	@NotifyChange({"newPassword","newPasswordError"})
	public void setNewPassword(final String newPassword) {
		newPasswordError = null;
		this.newPassword = newPassword;
	}
	
	public String getNewPasswordError() {
		return newPasswordError;
	}
	
	public String getRepeatNewPassword() {
		return repeatNewPassword;
	}
	
	@NotifyChange({"repeatNewPassword","repeatNewPasswordError"})
	public void setRepeatNewPassword(final String repeatNewPassword) {
		repeatNewPasswordError = null;
		this.repeatNewPassword = repeatNewPassword;
	}
	
	public String getRepeatNewPasswordError() {
		return repeatNewPasswordError;
	}
	
	public String getNombre() {
		return nombre;
	}
	
	public void setNombre(final String nombre) {
		this.nombre = nombre;
	}
	
	public String getApellidoPaterno() {
		return apellidoPaterno;
	}
	
	public void setApellidoPaterno(final String apellidoPaterno) {
		this.apellidoPaterno = apellidoPaterno;
	}
	
	public String getApellidoMaterno() {
		return apellidoMaterno;
	}
	
	public void setApellidoMaterno(final String apellidoMaterno) {
		this.apellidoMaterno = apellidoMaterno;
	}
	
	public List<Telefono> getTelefonos() {
		return telefonos;
	}
	
	public List<Email> getEmails() {
		return emails;
	}
	
	public Image getImage() {
		return image;
	}
	
	//Local Commands
	@Command @NotifyChange({"telefonos"})
	public void addPhone() {
		telefonos.add(new Telefono());
	}
	
	@Command @NotifyChange({"telefonos"})
	public void deletePhone(@BindingParam("telIndex") Integer telIndex) {
		if(!telefonos.isEmpty() && telefonos.get(telIndex)!=null) {
			telefonos.remove(telIndex.intValue());
		}
	}
	
	@Command @NotifyChange({"emails"})
	public void addEmail() {
		emails.add(new Email());
	}
	
	@Command @NotifyChange({"emails"})
	public void deleteEmail(@BindingParam("emailIndex") Integer emailIndex) {
		if(!emails.isEmpty() && emails.get(emailIndex)!=null) {
			emails.remove(emailIndex.intValue());
		}
	}
	
	@Command @NotifyChange({"image"})
	public void uploadImage(@ContextParam(ContextType.BIND_CONTEXT) 
			final BindContext ctx) {
		
		UploadEvent upEvent = (UploadEvent) ctx.getTriggerEvent();
		Media media = upEvent.getMedia();
		if(!(media instanceof Image)) {
			showImageNotSupported();
			return;
		}
		Image imageRetrieved = (Image) media;
		if(!CFDIConstants.SUPPORTED_IMAGES.contains(
				imageRetrieved.getFormat())) {
			showImageNotSupported();
			return;
		}
		imagenUsuario = new Imagen();
		imagenUsuario.setPersona(persona);
		imagenUsuario.setImagenPerfil(
				ByteUtils.toByteWrapper(imageRetrieved.getByteData()));
		imagenUsuario.setFormat(imageRetrieved.getFormat());
		try {
			image = ImageUtils.scaleToSize(imageRetrieved.getByteData(), 
					123, 123, imageRetrieved.getFormat()).getContent();
		} catch (IOException e) {
			log.error("Couldn't create thumnail from image");
			image = getDefaultProfileImage();
		}
	}
	
	@Command @NotifyChange({"oldPassword","oldPasswordError","newPassword",
		"newPasswordError","repeatNewPassword","repeatNewPasswordError"}) 
	public void performUserSave() {
		if(!isFormValid()){
			return;
		}
		usuario.setPassword(encryptionUtils.stringEncryption(newPassword));
		usuarioService.save(usuario);
		updateMessage();
		retrieveInitialUserInfo();
	}
	
	@Command @NotifyChange({"apellidoPaterno","apellidoMaterno","telefonos",
		"emails","image"})
	public void performProfileSave() {
		persona.setNombre(nombre);
		persona.setApellidoPaterno(apellidoPaterno);
		persona.setApellidoMaterno(apellidoMaterno);
		Contacto lastContacto = persona.getContacto();
		Contacto newContacto = new Contacto();
		for(Telefono currentTel : telefonos) {
			if(currentTel.getNumero()==null) {
				currentTel.setNumero("");
			}
			if(currentTel.getExtension()==null) {
				currentTel.setExtension("");
			}
			if(!currentTel.getNumero().trim().isEmpty() ||
					!currentTel.getExtension().trim().isEmpty()) {
				currentTel.setContacto(newContacto);
				newContacto.getTelefonos().add(currentTel);
			}
		}
		for(Email currentEmail : emails) {
			if(currentEmail.getEmail() != null && 
					!currentEmail.getEmail().trim().isEmpty()) {
				currentEmail.setContacto(newContacto);
				newContacto.getEmails().add(currentEmail);
			}
		}
		persona.setContacto(newContacto);
		personaService.save(persona);
		contactoService.delete(lastContacto);
		if(imagenUsuario != null) {
			imagenUsuario.setStatus(DBConstants.STATUS_ACTIVE);
			imagenService.save(imagenUsuario);
		}
		updateMessage();
		retrieveInitialProfileInfo();
		BindUtils.postGlobalCommand(null, null, "refreshProfileNavInfo", null);
	}
	
	@Command @NotifyChange({"oldPassword","oldPasswordError","newPassword",
		"newPasswordError","repeatNewPassword","repeatNewPasswordError"})
	public void performUserCancel() {
		retrieveInitialUserInfo();
	}
	
	@Command @NotifyChange({"apellidoPaterno","apellidoMaterno","telefonos",
		"emails","image"})
	public void performProfileCancel() {
		retrieveInitialProfileInfo();
	}
	
	//Global Commands
	
	
	//Private methods
	private void showImageNotSupported() {
		Messagebox.show("Imagen no soportada\n" +
				"formatos soportados (jpg, png, gif)");
	}
	
	private Image getDefaultProfileImage() {
		ClassLoader cl = getClass().getClassLoader();
		org.zkoss.zul.Image imageToSend = new org.zkoss.zul.Image();
		try {
			imageToSend.setContent(new AImage(cl.getResource("perfil/user.png")));
			return imageToSend.getContent();
		} catch (IOException e) {
			log.error("Error creating default profile pic");
			return null;
		}
	}
	
	private void retrieveInitialUserInfo() {
		oldPassword = null;
		oldPasswordError = null;
		newPassword = null;
		newPasswordError = null;
		repeatNewPassword = null;
		repeatNewPasswordError = null;
	}
	
	private void retrieveInitialProfileInfo() {
		imagenUsuario = null;
		retrieveInitialImage();
		nombre = usuario.getPersona().getNombre();
		apellidoPaterno = usuario.getPersona().getApellidoPaterno();
		apellidoMaterno = usuario.getPersona().getApellidoMaterno();
		retrieveInitialPhones();
		retrieveInitialEmails();
	}
	
	private void retrieveInitialImage() {
		Imagen imagen = imagenService.getDefaultImage(persona);
		if(imagen == null) {
			image = getDefaultProfileImage();
			return;
		}
		try {
			image = ImageUtils.scaleToSize(
					ByteUtils.toByteNative(imagen.getImagenPerfil()), 
					123, 123, imagen.getFormat()).getContent();
		} catch (IOException e) {
			log.error("Unable to retrieve saved image");
			image = getDefaultProfileImage();
		}
	}
	
	private void retrieveInitialPhones() {
		telefonos = new ArrayList<Telefono>();
		List<Telefono> toParse = telefonoService.getByContacto(
				persona.getContacto());
		for(Telefono currentParsePhone : toParse) {
			Telefono newPhone = new Telefono();
			newPhone.setTipoTelefono(currentParsePhone.getTipoTelefono());
			newPhone.setNumero(currentParsePhone.getNumero());
			newPhone.setExtension(currentParsePhone.getExtension());
			telefonos.add(newPhone);
		}
	}
	
	private void retrieveInitialEmails() {
		emails = new ArrayList<Email>();
		List<Email> toParse = emailService.getFromContacto(
				persona.getContacto());
		for(Email currentEmailParse : toParse) {
			Email newEmail = new Email();
			newEmail.setEmail(currentEmailParse.getEmail());
			emails.add(newEmail);
		}
	}
	
	private boolean isFormValid() {
		if(oldPassword != null) {
			if(!encryptionUtils.stringEncryption(oldPassword)
					.equals(usuario.getPassword())) {
				oldPasswordError = "Password incorrecto";
				return false;
			}
			if(newPassword != null) {
				if(!newPassword.matches(CFDIConstants.PASSWORD_EXPRESSION)) {
					newPasswordError = CFDIConstants.PASSWORD_REQUIREMENTS;
					return false;
				}
				if(repeatNewPassword == null) {
					repeatNewPasswordError = "Los passwords no coinciden";
					return false;
				}
				if(!newPassword.equals(repeatNewPassword)) {
					repeatNewPasswordError = "Los passwords no coinciden";
					return false;
				}
				return true;
			}
			else {
				newPasswordError = "Se necesita llenar este campo";
				return false;
			}
		}
		return false;
	}
	
	private void updateMessage() {
		Messagebox.show("Los datos se guardaron correctamente");
	}
}
